////////////////////////////////////////////////////////////////////////////
//
//  Crytek Engine Source File.
//  Copyright (C), Crytek Studios, 2001-2005.
// -------------------------------------------------------------------------
//  File name:   FlowSoundDialog.cpp
//  Version:     v1.00
//  Created:     Jun 22th 2006 by TomasN
//  Compilers:   Visual Studio.NET 2005
//  Description: 
// -------------------------------------------------------------------------
//  History: copied from PlaySoundNode.cpp
//
////////////////////////////////////////////////////////////////////////////

#include "StdAfx.h"
#include <IEntityProxy.h>
#include <ISound.h>
#include "FlowBaseNode.h"
#include "IGameTokens.h"

#define SAFE_SOUND_UNREGISTER
// #undef  SAFE_SOUND_UNREGISTER

class CFlowNode_SoundDialog : public CFlowBaseNode<eNCT_Instanced>, public ISoundEventListener
{

public:
	CFlowNode_SoundDialog( SActivationInfo * pActInfo )
		:	m_curEntityId(0),
		m_pGraph(NULL),
		m_myID(InvalidFlowNodeId),
		m_SoundID(INVALID_SOUNDID),
		m_bPrecacheOnce(true),
		m_bAudioIsPrecached(false)
	{
	}

	~CFlowNode_SoundDialog()
	{
		ISound* pSound = GetSound();
		if (pSound) 
		{
			pSound->RemoveEventListener(this);
			pSound->Stop(); // use pSound directly as soundproxy might already be deleted
		}
	}

	IFlowNodePtr Clone( SActivationInfo * pActInfo )
	{
		return new CFlowNode_SoundDialog(pActInfo);
	}

	virtual void Serialize(SActivationInfo* pActInfo, TSerialize ser)
	{
		ser.BeginGroup("Local");
		if (ser.IsWriting()) 
		{
			bool playing = m_SoundID != INVALID_SOUNDID;
			ser.Value("playing", playing);
		} 
		else 
		{
			// On Reading we stop any previously playing sounds... proper serialization should be done in via MusicSystem
			StopSound();

			if (pActInfo->pEntity != 0) 
				m_curEntityId = pActInfo->pEntity->GetId();
			else
				m_curEntityId = 0;

		}
		ser.EndGroup();
	}

	enum INPUTS 
	{ 
		eIN_NAME = 0,
		eIN_PLAY,
		eIN_STOP,
		eIN_PRECACHE,
		eIN_NAME2,
		eIN_DIALOG,
		eIN_DIALOGLOUD,
	};

	enum OUTPUTS 
	{
		eOUT_DONE = 0,
		eOUT_PLAYING,
		eOUT_PRECACHE_SUCCESS
	};

	virtual void GetConfiguration(SFlowNodeConfig& config)
	{
		static const SInputPortConfig inputs[] = 
		{
			InputPortConfig<string> ("sound_SoundName", _HELP("Name of Dialog Sound to play"), _HELP("SoundName")),
			InputPortConfig_Void ("PlayTrigger", _HELP("Play"), _HELP("Play")),
			InputPortConfig_Void ("StopTrigger", _HELP("Stop"), _HELP("Stop")),
			InputPortConfig_Void ("PrecacheTrigger", _HELP("Precaches the audio data."), _HELP("Precache")),
			InputPortConfig<string> ("sound_SoundNameLoud", _HELP("(optional) Name of loud dialog sound to play"), _HELP("SoundNameLoud")),
			InputPortConfig<string> ("dialogline_DialogLine", _HELP("Name of Dialog Line to play"), _HELP("DialogLine")),
			InputPortConfig<string> ("dialogline_DialogLineLoud", _HELP("(optional) Name of loud Dialog line to play"), _HELP("DialogLineLoud")),
			InputPortConfig_Null()
		};
		static const SOutputPortConfig outputs[] = 
		{
			OutputPortConfig_Void    ("Done", _HELP("Triggered if Dialog has stopped playing")),
			OutputPortConfig<bool>   ("IsPlaying", _HELP("True if Dialog is playing (triggered on changed)")),
			OutputPortConfig<bool>   ("PrecacheSuccess", _HELP("True if the precaching of audio data was successful")),
			OutputPortConfig_Null()
		};    
		config.nFlags |= EFLN_TARGET_ENTITY;
		config.pInputPorts = inputs;
		config.pOutputPorts = outputs;
		config.sDescription = _HELP("Play a DialogSound on an entity");
		config.SetCategory(EFLN_APPROVED);
	}

	// side effects: m_SoundID will be INVALID_SOUNDID
	void StopSound()
	{
		if (m_SoundID != INVALID_SOUNDID)
		{
			IEntitySoundProxy *pSoundProxy = GetSoundProxy(m_curEntityId);
			if (pSoundProxy == NULL) 
			{
				if (m_curEntityId != 0)
					CryLogAlways("[flow] Sound:StopDialog - Can't get sound proxy of entity!");
			} 
			else
			{
				pSoundProxy->StopSound(m_SoundID);
			}
			ISound* pSound = GetSound();
			if (pSound) 
			{
				pSound->RemoveEventListener(this);
			}
			m_SoundID = INVALID_SOUNDID;
		}
	}

	// side effect: m_SoundID will be valid (on success)
	// returns true if sound started, false otherwise
	bool StartSound(SActivationInfo *pActInfo)
	{
		static const bool bDontPlaySameSound = true;

#ifdef SAFE_SOUND_UNREGISTER
		// we have to do this here
		// because otherwise we could end up in a situation that two sounds have been triggered
		// and we registered at both of them. but none of them has called as back
		// but we can only unregister from one in our d'tor. so we have to unregister here
		if (m_SoundID != INVALID_SOUNDID && !m_bAudioIsPrecached)
		{
			ISound* pSound = GetSound();
			if (pSound)
			{
				if (bDontPlaySameSound)
				{
					const string& soundName = GetSoundName(pActInfo); //GetPortString(pActInfo, eIN_NAME);
					if (soundName.compare(pSound->GetName()) == 0)
					{
						return false;
					}
				}
				pSound->RemoveEventListener(this);
			}
			m_SoundID = INVALID_SOUNDID;
		}
#endif

		IEntity *pEnt = pActInfo->pEntity;
		if (!pEnt)
			return false;

		IEntitySoundProxy *pSoundProxy = static_cast<IEntitySoundProxy*> (GetOrMakeProxy(pEnt, ENTITY_PROXY_SOUND));
		m_curEntityId = pSoundProxy ? pEnt->GetId() : 0;

		if (!pSoundProxy)
		{
			CryLogAlways("[flow] Sound:Dialog - Can't get sound proxy of entity!");
			return false;
		}

		// start a new sound
		int sFlags = FLAG_SOUND_DEFAULT_3D | FLAG_SOUND_EVENT | FLAG_SOUND_VOICE;
		sFlags |= IsClientActor(m_curEntityId) ? FLAG_SOUND_RELATIVE : 0;
		
		ISound* pSound				= NULL;
		
		if (!m_bAudioIsPrecached)
		{
			const string &sSound	= GetSoundName(pActInfo);
			m_SoundID	= INVALID_SOUNDID;
			pSound		= gEnv->pSoundSystem->CreateSound(sSound,sFlags); //GetPortString(pActInfo, eIN_NAME), sFlags);
			
			if (!pSound) 
			{
				CryLogAlways("[flow] Sound:Dialog - Can't play sound '%s'!", sSound.c_str());
				return false;
			}
		}
		else
		{
			// We have a pre-cached sound
			pSound = GetSound();
		}

		// set properties and register as listener to get notified on stop
		if (pSound != NULL) 
		{
			// register as listener
			pSound->AddEventListener(this, "FlowSoundDialog");
			m_SoundID = pSound->GetId();
			pSound->SetSemantic(eSoundSemantic_Dialog);

			// set some properties ...
			// sound proxy uses head pos on dialog sounds
			pSoundProxy->PlaySound(pSound, Vec3(ZERO), FORWARD_DIRECTION, 1.0f);

			// signal we're playing because the START_PAUSED does not work currently
			ActivateOutput(pActInfo,eOUT_PLAYING, true);
			return true;
		}

		return false; // we should never come here...
	}

	ISound* GetSound()
	{
		if (m_SoundID != INVALID_SOUNDID)
			return gEnv->pSoundSystem->GetSound( m_SoundID );

		return 0;
	}

	virtual void ProcessEvent( EFlowEvent event, SActivationInfo *pActInfo )
	{
		switch (event)
		{
		case eFE_Initialize:
			{
				IEntity *pEnt = pActInfo->pEntity; // might be 0 when dynamically set / ActionGraphs

				StopSound();
				m_pGraph = pActInfo->pGraph;
				m_myID = pActInfo->myID;
				m_curEntityId = pEnt ? pEnt->GetId() : 0;

				if (m_bPrecacheOnce)
				{
					// Only always pre-cache if we're running the editor
					if (gEnv->IsEditor())
					{
						const string& soundName = GetSoundName(pActInfo); //GetPortString(pActInfo, eIN_NAME);
						gEnv->pSoundSystem->Precache(soundName.c_str(), FLAG_SOUND_VOICE, FLAG_SOUND_PRECACHE_DIALOG_DEFAULT);
					}

					m_bPrecacheOnce = false;
				}
			}
			break;

		case eFE_SetEntityId:
			{
				StopSound(); 
				IEntity *pEnt = pActInfo->pEntity;
				m_curEntityId = pEnt ? pEnt->GetId() : 0;
			}
			break;

		case eFE_Activate:
			{
//				bool bStarted = false;
				if ( IsPortActive(pActInfo, eIN_STOP)) 
				{
					StopSound();
					// TODO: only activate outputs if it was actually playing before stopping
					ActivateOutput(pActInfo, eOUT_DONE, true);
					ActivateOutput(pActInfo, eOUT_PLAYING, false);
				}

				if (IsPortActive(pActInfo, eIN_PLAY)) 
				{
					StartSound(pActInfo);
	//				bStarted = true;
				}

				if (IsPortActive(pActInfo, eIN_PRECACHE))
				{
					PrecacheAudio(pActInfo);
				}
			}
			break;
		}
	}

	//////////////////////////////////////////////////////////////////////////
	// ISoundEventListener implementation
	//////////////////////////////////////////////////////////////////////////
	virtual void OnSoundEvent( ESoundCallbackEvent event,ISound *pSound ) 
	{
		switch (event) 
		{
		case SOUND_EVENT_ON_STOP:
			{
				// CryLogAlways("[flow] Sound:PlaySound - event stop");
				if (m_pGraph)
				{
					SFlowAddress addr (m_myID, eOUT_PLAYING, true);
					m_pGraph->ActivatePort(addr, false);
					addr.port = eOUT_DONE;
					m_pGraph->ActivatePort(addr, true);				
					if (pSound) // we remove from the sound which called us back
					{
						pSound->RemoveEventListener(this);
						if (pSound->GetId() == m_SoundID) 
						{
							// only set the m_SoundID to invalid if this was the sound which we're currently playing
							// it could be the case that a new sound with a new ID has been triggered but the OLD sound calls us back
							m_SoundID = INVALID_SOUNDID;

							// Make sure the "PrecacheSuccess" output address is turned off once the sound was stopped
							addr.port = eOUT_PRECACHE_SUCCESS;
							m_pGraph->ActivatePort(addr, false);

							// Also turn off the Play port
							addr.port			= eIN_PLAY;
							addr.isOutput	= false;
							m_pGraph->ActivatePort(addr, false);

							// Indicate that our pre-cache information is outdated
							m_bAudioIsPrecached = false;
						}
					}
				}
			}
			break;
		case SOUND_EVENT_ON_START:
			{
				SFlowAddress addr (m_myID, eOUT_PLAYING, true);
				if (m_pGraph) m_pGraph->ActivatePort(addr, true);
			}
			break;
		case SOUND_EVENT_ON_LOADED:
			{
				// We care only about this callback if our sound is not in the process of being played
				if (!pSound->IsPlaying() && pSound->GetId() == m_SoundID)
				{
					SFlowAddress const oFlowAddress(m_myID, eOUT_PRECACHE_SUCCESS, true);
					m_pGraph->ActivatePort(oFlowAddress, true);
					m_bAudioIsPrecached = true;
				}
			}
			break;
		}
	}

	virtual void GetMemoryUsage(ICrySizer * s) const
	{
		s->Add(*this);
	}

protected:

	const string &GetSoundName(SActivationInfo *pActInfo)
	{
		// check if old soundname is used or new dialogline field
		INPUTS eDefaultPort = eIN_DIALOG;
		INPUTS eLoudPort = eIN_DIALOGLOUD;

		const string &sNewDialog = GetPortString(pActInfo, eIN_DIALOG);
		const string &sNewDialogLoud = GetPortString(pActInfo, eIN_DIALOGLOUD);
		
		if (sNewDialog.empty() && sNewDialogLoud.empty())
		{
			eDefaultPort = eIN_NAME;
			eLoudPort = eIN_NAME2;
		}

		const string &sDialogLoud = GetPortString(pActInfo, eLoudPort);

		float fBattleValue = 0.0f;
		
		// query BattleStatus value if there is a loud sound
		if (!sDialogLoud.empty() && gEnv->pGame->GetIGameFramework())
		{
			IGameTokenSystem *pGameTokenSystem = gEnv->pGame->GetIGameFramework()->GetIGameTokenSystem();		

			if (pGameTokenSystem)
			{
				IGameToken	*pToken = pGameTokenSystem->FindToken("game.BattleStatus");

				if (pToken)
					pToken->GetValueAs(fBattleValue);
			}
		}

		if (fBattleValue > 0.01f)
			return(sDialogLoud); // use new dialog sound
		else
			return(GetPortString(pActInfo, eDefaultPort)); // use default
	}

private:

	void PrecacheAudio(SActivationInfo* const pActInfo)
	{
		// Make sure the "PrecacheSuccess" output address is turned off
		SFlowAddress const oFlowAddress(m_myID, eOUT_PRECACHE_SUCCESS, true);
		m_pGraph->ActivatePort(oFlowAddress, false);

		string const& sSound	= GetSoundName(pActInfo);
		int nFlags						= FLAG_SOUND_DEFAULT_3D | FLAG_SOUND_EVENT | FLAG_SOUND_VOICE;
		nFlags								|= IsClientActor(m_curEntityId) ? FLAG_SOUND_RELATIVE : 0;
		m_SoundID							= INVALID_SOUNDID;

		ISound* const pSound = gEnv->pSoundSystem->CreateSound(sSound.c_str(), nFlags);
		if (pSound) 
		{
			if (pSound->IsLoaded())
			{
				m_pGraph->ActivatePort(oFlowAddress, true);
			}
			else
			{
				pSound->AddEventListener(this, "FlowSoundDialog");
			}

			m_SoundID = pSound->GetId();
		}
		else
			CryLogAlways("[flow] Sound:Dialog - Can't prime sound '%s'!", sSound.c_str());
	}

	EntityId    m_curEntityId;
	IFlowGraph *m_pGraph;
	TFlowNodeId m_myID;
	tSoundID    m_SoundID; 
	bool				m_bPrecacheOnce;
	bool				m_bAudioIsPrecached;
};


REGISTER_FLOW_NODE("Sound:Dialog", CFlowNode_SoundDialog);
